using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Polymod.TemplateBuilders
{
    public class ModelInvoker : IInvoker
    {
        IInvoker _innerInvoker;
        IModelFactory _modelFactory;
        volatile bool _needsRead = true;
        object _cachedValue;

        /// <summary>
        /// Gets InnerInvoker
        /// </summary>
        public IInvoker InnerInvoker
        {
            get { return _innerInvoker; }
        }

        public ModelInvoker(IInvoker innerInvoker, IModelFactory modelFactory)
        {
            if (modelFactory == null) throw new System.ArgumentNullException(Constants.ModelFactory);
            if (innerInvoker == null) throw new System.ArgumentNullException("innerInvoker");
            _innerInvoker = innerInvoker;
            _modelFactory = modelFactory;
        }

        public object GetValue(IModel target)
        {
            if (_needsRead)
            {
                _needsRead = false;
                var innerValue = _innerInvoker.GetValue(target);
                if (innerValue == null)
                    _cachedValue = null;
                else if (_modelFactory.IsModelCandidate(innerValue.GetType()))
                    //Create model only if the factory thinks this is a valid candidate for a model.
                    _cachedValue = _modelFactory.GetModel(new ParentModelInfo(target, this.Name), innerValue);
                else
                    _cachedValue = innerValue;
            }
            return _cachedValue;
        }

        public void SetValue(IModel target, object value)
        {
            _innerInvoker.SetValue(target, value);
            _needsRead = true;
        }


        public Type ReturnType
        {
            get
            {
                if (_modelFactory.IsModelCandidate(_innerInvoker.ReturnType))
                    return typeof(IModel);
                else
                    return _innerInvoker.ReturnType;
            }
        }

        IInvoker IInvoker.Clone()
        {
            return this.Clone();
        }
        public ModelInvoker Clone()
        {
            var result = new ModelInvoker(_innerInvoker.Clone(), _modelFactory);
            return result;
        }


        public bool IsReadonly
        {
            get { return _innerInvoker.IsReadonly; }
        }


        public string Name
        {
            get { return _innerInvoker.Name; }
        }
    }
}
